/*
 *  Для компіляції програми потрібно виконати команду
 *  gcc add_vector.c -I /usr/local/cuda/include/CL -L /usr/lib64/ -lOpenCL -o add_vector.run
 *  Для виконання програми потрібно виконати команду
 *  ./add_vector.run
 */
// підключення стандартних бібліотек ввеедення виведення
#include <stdio.h>
#include <stdlib.h>
// підключення заголовного файлу OpenCL
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
// опис змінних і констант
#define MAX_SOURCE_SIZE (0x100000)

int main(void) {
 		// Створення двох вхідних векторів
 int i;
 const int LIST_SIZE = 1024;
 // оголошення і ініціалізація вхідних масивів A i B
 int *A = (int*)malloc(sizeof(int)*LIST_SIZE);
 int *B = (int*)malloc(sizeof(int)*LIST_SIZE);
 for(i = 0; i<LIST_SIZE; i++) {
     A[i] = i;
     B[i] = LIST_SIZE - i;
 }

   // Завантаження вихідного коду ядра з файлу в масив символів source_str
 FILE *fp;
 char *source_str;
 size_t source_size;

 fp = fopen("vector_add_kernel.cl", "r");
 if (!fp) {
     fprintf(stderr, "Failed to load kernel.\n");
     exit(1);
 }
 source_str = (char*)malloc(MAX_SOURCE_SIZE);
 source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
 fclose( fp );

 // отримання інформації про платформу та пристрій
 cl_platform_id platform_id = NULL;
 cl_device_id device_id = NULL;   
 cl_uint ret_num_devices;
 cl_uint ret_num_platforms;
 cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
 ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_DEFAULT, 1, 
         &device_id, &ret_num_devices);

 // створення контексту OpenCL-програми
 cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &ret);

 // створення черги команд
cl_command_queue command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
 // створення буферу пам'яті об’єктів
 cl_mem a_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY, 
         LIST_SIZE * sizeof(int), NULL, &ret);
 cl_mem b_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
         LIST_SIZE * sizeof(int), NULL, &ret);
 cl_mem c_mem_obj = clCreateBuffer(context, CL_MEM_WRITE_ONLY, 
         LIST_SIZE * sizeof(int), NULL, &ret);

 // передача даних (вектор А та В) для буферів пам'яті на пристрої
 ret = clEnqueueWriteBuffer(command_queue, a_mem_obj, CL_TRUE, 0,
         LIST_SIZE * sizeof(int), A, 0, NULL, NULL);
 ret = clEnqueueWriteBuffer(command_queue, b_mem_obj, CL_TRUE, 0, 
         LIST_SIZE * sizeof(int), B, 0, NULL, NULL);

 // створення програмного об'єкту
 cl_program program = clCreateProgramWithSource(context, 1,(const char **)&source_str, (const size_t *)&source_size, &ret);
 // побудова програми
 ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);

 // створення об’єкту ядра
 cl_kernel kernel = clCreateKernel(program, "vector_add", &ret);

 // встановлення аргументів ядра
 ret=clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&a_mem_obj);
 ret=clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&b_mem_obj);
 ret=clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&c_mem_obj);

 // Виконaння ядра програми
 size_t global_item_size = LIST_SIZE; // Опрацювання розміру вектора
 size_t local_item_size = 64; // опрацювання одного елемента за час
 ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, 
         &global_item_size, &local_item_size, 0, NULL, NULL);

//зчитування пам'яті буфера пам'яті С на пристрій для локальної змінної С    
 int *C = (int*)malloc(sizeof(int)*LIST_SIZE);
 ret = clEnqueueReadBuffer(command_queue, c_mem_obj, CL_TRUE, 0, 
         LIST_SIZE * sizeof(int), C, 0, NULL, NULL);

 // вивід результатів на екран
 for(i = 0; i < LIST_SIZE; i++)
     printf("%d + %d = %d\n", A[i], B[i], C[i]);

 // звільнення пам'яті
 ret = clFlush(command_queue);
 ret = clFinish(command_queue);
 ret = clReleaseKernel(kernel);
 ret = clReleaseProgram(program);
 ret = clReleaseMemObject(a_mem_obj);
 ret = clReleaseMemObject(b_mem_obj);
 ret = clReleaseMemObject(c_mem_obj);
 ret = clReleaseCommandQueue(command_queue);
 ret = clReleaseContext(context);
 free(A);
 free(B);
 free(C);
 return 0;
 }
